//! Provides C-like macros: offset_of and container_of
//!
//! # Examples
//!
//! ```rust
//!
//!	extern crate hioff as hun;
//!
//! #[repr(C)]
//! struct Bar {
//! 	key:	i32,
//!		value:	i32,
//! }
//!
//! #[repr(C)]
//! struct Foo {
//!		key:	i32,
//!		value:	[Bar; 2],
//! }
//!
//!	assert_eq!(hun::offset_of!(Bar, value), 4);
//! assert_eq!(hun::offset_of!(Foo, value[1].key), 12);
//!
//! let foo = Foo {
//!		key: 1,
//!		value: [ Bar { key: 2, value: 2}, Bar { key: 3, value: 3 }],
//!	};
//!	let value = &foo.value[1].value;
//!
//! let obj = unsafe { hun::container_of!(value, Foo, value[1].value) };
//!	assert_eq!(obj as *const _, &foo as *const _);
//! ```
//!

extern crate proc_macro;
use std::time::{ SystemTime };
use proc_macro::{ TokenStream };
use proc_macro2::{ Span };
use quote::quote;
use syn::{
	Expr, Token, Path, parse_macro_input,
	spanned::{ Spanned },
	parse::{self, Parse, ParseStream },
	punctuated::{ Punctuated },
};

#[proc_macro]
pub fn offset_of(input: TokenStream) -> TokenStream {
	let input = parse_macro_input!(input as OffsetOfInput);
	let parent = &input.parent;
	let member = &input.member;
	let base = make_ident("base", parent.span());
	quote! {
		unsafe {
			let #base = ::core::ptr::null::<#parent>();
			::core::ptr::addr_of!((*#base).#member) as *const u8 as usize
		}
	}.into()
}

#[proc_macro]
pub fn container_of(input: TokenStream) -> TokenStream {
	container_of_impl(input, false)
}

#[proc_macro]
pub fn container_of_mut(input: TokenStream) -> TokenStream {
	container_of_impl(input, true)
}

struct OffsetOfInput {
	parent: Path,
	_comma: Option<Token![,]>,
	member:	Punctuated<Expr, Token![.]>,
}

impl Parse for OffsetOfInput {
	fn parse(input: ParseStream) -> parse::Result<Self> {
		Ok(OffsetOfInput {
			parent: input.parse()?,
			_comma: input.parse()?,
			member: Punctuated::parse_terminated(input)?,
		})
	}
}

fn container_of_impl(input: TokenStream, is_mut: bool) -> TokenStream {
	let input = parse_macro_input!(input as ContainerOfInput);
	let tokens = container_of_tokens(&input);
	if is_mut {
		quote!({ &mut *(#tokens) }).into()
	} else {
		quote!({ &*(#tokens) }).into()
	}
}

fn container_of_tokens(input: &ContainerOfInput) -> proc_macro2::TokenStream {
	let obj = &input.obj;
	let parent = &input.offset.parent;
	let member = &input.offset.member;
	let base = make_ident("base", parent.span());
	let offset = make_ident("offset", member.span());
	quote!({
		let #base = ::core::ptr::null::<#parent>();
		let #offset = ::core::ptr::addr_of!((*#base).#member) as *const u8 as usize;
		if ::core::mem::size_of_val(&((*#base).#member)) ==
			::core::mem::size_of_val(#obj) {
			let #base = (#obj) as *const _ as *const u8 as usize;
			(#base - #offset) as *mut u8 as *mut #parent
		} else {
			core::panic!("container_of: wrong arguments!");
		}
	})
}

struct ContainerOfInput {
	obj:	Expr,
	_comma:	Option<Token![,]>,
	offset:	OffsetOfInput,
}

impl Parse for ContainerOfInput {
	fn parse(input: ParseStream) -> syn::Result<Self> {
		Ok(ContainerOfInput {
			obj:	input.parse()?,
			_comma:	input.parse()?,
			offset:	OffsetOfInput {
				parent:	input.parse()?,
				_comma:	input.parse()?,
				member:	Punctuated::parse_terminated(input)?,
			},
		})
	}
}

fn make_ident(name: &str, span: Span) -> syn::Ident {
	let name = format!("{}_{}", name, SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs());
	syn::Ident::new(&name, span)
}
